﻿using System;
using System.Diagnostics;
using System.Windows.Input;
using Microsoft.VisualStudio.Text.Editor;

namespace KineticScroll
{
	/// <summary>
	/// Overrides Visual Studio mouse bindings to provide kinetic scrolling.
	/// </summary>
	internal sealed class KineticScrollMouseProcessor : MouseProcessorBase
	{
		#region Variables

		private IWpfTextView _textView;

		private KineticScroller _scrollHelper;
		private double _currentPosition;

		#endregion Variables

		#region Constructor

		/// <summary>
		/// Initializes a new instance of the <see cref="KineticScrollMouseProcessor"/> class.
		/// </summary>
		/// <param name="textView">The text view.</param>
		public KineticScrollMouseProcessor(IWpfTextView textView)
		{
			try
			{
				_textView = textView;

				// Initialize our scroll helper with the text view's height
				_scrollHelper = new KineticScroller(Settings.Instance.RefreshInterval, Settings.Instance.Deceleration, Settings.Instance.MaxVelocity, _textView.ViewportHeight);

				// Listen for changes to settings and the text view closing so that we can unhook those listeners
				Settings.Instance.RefreshIntervalChanged += new EventHandler(Settings_RefreshIntervalChanged);
				Settings.Instance.DecelerationChanged += new EventHandler(Settings_DecelerationChanged);
				Settings.Instance.MaxVelocityChanged += new EventHandler(Settings_MaxVelocityChanged);
				_textView.ViewportHeightChanged += new EventHandler(TextView_ViewportHeightChanged);
				_textView.Closed += new EventHandler(TextView_Closed);

				// Listen for changes to the scroll helper's Position property so that we can update the text view's scroll position
				_scrollHelper.PositionChanged += new EventHandler(ScrollHelper_PositionChanged);
			}
			catch (Exception ex)
			{
				Trace.WriteLine("KineticScrollMouseProcessor:  " + ex.Message);
			}
		}

		private void TextView_ViewportHeightChanged(object sender, EventArgs e)
		{
			_scrollHelper.ScrollRange = _textView.ViewportHeight;
		}

		private void Settings_RefreshIntervalChanged(object sender, EventArgs e)
		{
			_scrollHelper.RefreshInterval = Settings.Instance.RefreshInterval;
		}

		private void Settings_DecelerationChanged(object sender, EventArgs e)
		{
			_scrollHelper.Deceleration = Settings.Instance.Deceleration;
		}

		private void Settings_MaxVelocityChanged(object sender, EventArgs e)
		{
			_scrollHelper.MaxVelocity = Settings.Instance.MaxVelocity;
		}

		private void TextView_Closed(object sender, EventArgs e)
		{
			Settings.Instance.RefreshIntervalChanged -= new EventHandler(Settings_RefreshIntervalChanged);
			Settings.Instance.DecelerationChanged -= new EventHandler(Settings_DecelerationChanged);
			Settings.Instance.MaxVelocityChanged -= new EventHandler(Settings_MaxVelocityChanged);
		}

		#endregion Constructor

		#region Scrolling Methods

		private void ScrollHelper_PositionChanged(object sender, EventArgs e)
		{
			try
			{
				double newPosition = _scrollHelper.Position;
				_textView.ViewScroller.ScrollViewportVerticallyByPixels(newPosition - _currentPosition);
				_currentPosition = newPosition;
			}
			catch (Exception ex)
			{
				Trace.WriteLine("ScrollHelper_PositionChanged:  " + ex.Message);
			}
		}

		/// <summary>
		/// Handles the mouse down event before the default handler.
		/// </summary>
		/// <param name="e">The event arguments.</param>
		public override void PreprocessMouseDown(MouseButtonEventArgs e)
		{
			_scrollHelper.StopScrollAnimation();

			if (Settings.Instance.MiddleClickEnabled && e.ChangedButton == MouseButton.Middle)
			{
				try
				{
					double startPosition = e.GetPosition(_textView.VisualElement).Y;
					_scrollHelper.StartUserScroll(startPosition);

					_currentPosition = startPosition;

					Mouse.OverrideCursor = Cursors.Hand;
					e.Handled = true;
				}
				catch (Exception ex)
				{
					Trace.WriteLine("PreprocessMouseDown:  " + ex.Message);
				}
			}
		}

		/// <summary>
		/// Handles the mouse move event before the default handler.
		/// </summary>
		/// <param name="e">The event arguments.</param>
		public override void PreprocessMouseMove(MouseEventArgs e)
		{
			if (Settings.Instance.MiddleClickEnabled && Mouse.MiddleButton == MouseButtonState.Pressed)
			{
				try
				{
					double newPosition = e.GetPosition(_textView.VisualElement).Y;
					_scrollHelper.UpdateUserScroll(newPosition);

					e.Handled = true;
				}
				catch (Exception ex)
				{
					Trace.WriteLine("PreprocessMouseMove:  " + ex.Message);
				}
			}
		}

		/// <summary>
		/// Handles the mouse up event before the default handler.
		/// </summary>
		/// <param name="e">The event arguments.</param>
		public override void PreprocessMouseUp(MouseButtonEventArgs e)
		{
			if (Settings.Instance.MiddleClickEnabled && e.ChangedButton == MouseButton.Middle)
			{
				try
				{
					// TODO: Scroll to the nearest whole line?
					_scrollHelper.StopUserScroll();

					Mouse.OverrideCursor = null;
					e.Handled = true;
				}
				catch (Exception ex)
				{
					Trace.WriteLine("PreprocessMouseUp:  " + ex.Message);
				}
			}
		}

		/// <summary>
		/// Handles the mouse wheel event after the default handler.
		/// </summary>
		/// <param name="e">The event arguments.</param>
		public override void PreprocessMouseWheel(MouseWheelEventArgs e)
		{
			_scrollHelper.StopScrollAnimation();

			if (Settings.Instance.MouseWheelEnabled)
			{
				try
				{
					// Delta is always 120, so we just divide by 120 to get plus or minus 1 for the direction
					int multiplier = e.Delta / 120;

					// Change the velocity by the acceleration factor from settings
					_scrollHelper.ChangeVelocity(multiplier * Settings.Instance.MouseWheelAccelerationFactor);

					e.Handled = true;
				}
				catch (Exception ex)
				{
					Trace.WriteLine("PreprocessMouseWheel:  " + ex.Message);
				}
			}
		}

		#endregion Scrolling Methods
	}
}
